1 module hip.ai.decisiontree; 2 version(Test): 3 4 enum ResultTypes 5 { 6 _string, 7 _bool, 8 _int, 9 _uint, 10 _ushort, 11 _ulong, 12 _float, 13 _double, 14 _char, 15 _byte, 16 _ubyte, 17 _void, 18 _null, 19 _Condition 20 } 21 22 struct ConditionResult 23 { 24 import hip.util.memory; 25 ResultTypes type; 26 void* value; 27 28 T* get(T)(){return cast(T*)value;} 29 static ConditionResult create(T)(T val) 30 { 31 static if(is(T == void*)) 32 return ConditionResult(ResultTypes._void, val); 33 else static if(__traits(compiles, mixin("ResultTypes._"~T.stringof))) 34 return ConditionResult(mixin("ResultTypes._"~T.stringof), toHeap(val)); 35 else 36 static assert(false, "Could not create result of type "~T.stringof); 37 } 38 39 static ConditionResult nullResult(){return ConditionResult(ResultTypes._null, null);} 40 41 void dispose() 42 { 43 if(value != null) 44 { 45 free(value); 46 value = null; 47 type = ResultTypes._null; 48 } 49 } 50 } 51 52 struct Condition 53 { 54 string name; 55 bool function(void* context) condition; 56 ConditionResult trueResult = ConditionResult.nullResult; 57 ConditionResult falseResult = ConditionResult.nullResult; 58 } 59 60 61 /** 62 63 DecisionTree intuition: 64 65 IsVisible: 66 true-> Returns PlayerMovement.run 67 false-> Returns a new branch if is hearing 68 IsHearing: 69 true-> Returns "They're hearing you!" 70 false-> Returns null 71 72 Functions receive a void* context for accepting any data 73 You can set true or false results for branches by executing the following code 74 `tree.setTrueBranch("IsVisible.no.IsHearing", ConditionResult.create("They're hearing you!");` 75 76 This will make the true branch result at isVisible(false branch) gets the value of the string. 77 78 Basic example on how the decision tree works: 79 80 HipDecisionTree playerAI = new HipDecisionTree("Enemy-based PlayerAI"); 81 82 playerAI.addCondition(Condition("IsVisible", 83 (void* ctx) 84 { 85 PlayerAIDecision* d = cast(PlayerAIDecision*)ctx; 86 writeln("Checking if visible"); 87 return d.isVisible; 88 })); 89 playerAI.setTrueBranch("IsVisible", ConditionResult.create(cast(int)PlayerMovement.run)) 90 .setFalseBranch("IsVisible", ConditionResult.create(Condition("IsHearing", 91 (void* ctx) 92 { 93 PlayerAIDecision* d = cast(PlayerAIDecision*)ctx; 94 return d.isHearing; 95 }) 96 )); 97 playerAI.setTrueBranch("IsVisible.no.IsHearing", ConditionResult.create("They're hearing you!")); 98 99 PlayerAIDecision d; 100 d.isVisible = false; 101 d.isHearing = false; 102 ConditionResult res = playerAI.check("IsVisible", &d); 103 if(res.type == ResultTypes._int) 104 { 105 writeln(*cast(int*)res.value); 106 } 107 else if(res.type == ResultTypes._string) 108 { 109 writeln(*cast(string*)res.value); 110 } 111 */ 112 113 class HipDecisionTree 114 { 115 Condition[string] conditions; 116 import hip.util.string : split; 117 string name; 118 this(string name) 119 { 120 this.name = name; 121 } 122 123 HipDecisionTree addCondition(Condition c) 124 { 125 conditions[c.name] = c; 126 return this; 127 } 128 129 HipDecisionTree setTrueBranch(string conditionName, ConditionResult res) 130 { 131 getConditionByName(conditionName).trueResult = res; 132 return this; 133 } 134 HipDecisionTree setFalseBranch(string conditionName, ConditionResult res) 135 { 136 getConditionByName(conditionName).falseResult = res; 137 return this; 138 } 139 140 ConditionResult check(string which, void* ctx) 141 { 142 assert((which in conditions) != null, "Could not find "~which); 143 Condition* c = which in conditions; 144 ConditionResult res; 145 146 while(true) 147 { 148 bool isTrue = c.condition(ctx); 149 if(isTrue) 150 res = c.trueResult; 151 else 152 res = c.falseResult; 153 if(res.type == ResultTypes._Condition) 154 c = cast(Condition*)res.value; 155 else 156 return res; 157 } 158 } 159 protected Condition* getConditionByName(string name) 160 { 161 string[] names = name.split("."); 162 string lastName = names[0]; 163 Condition* c = lastName in conditions; 164 if(names.length == 1) 165 return c; 166 assert(names.length % 2 != 0, "Names must not be divisible by 2. They must have a pre accessor as 'yes' or 'no'"); 167 168 bool isTrue; 169 for(int i = 1; i < names.length; i+= 2) 170 { 171 isTrue = names[i] == "yes"; 172 if(isTrue) 173 c = c.trueResult.get!Condition; 174 else 175 c = c.falseResult.get!Condition; 176 } 177 return c; 178 } 179 180 }